﻿/*
 * hisdatamgr.cpp
 *
 *  Created on: 2017年7月13日
 *      Author: work
 */

#include "hisdatamgr.hpp"

#include <dm/os/log/logger.hpp>
#include <dm/env/env.hpp>
#include <tinyxml.h>

#include <dm/scada/devicemgr.hpp>
#include <dm/scada/logicdevicemgr.hpp>
#include <dm/scada/statusmgr.hpp>
#include <dm/scada/discretemgr.hpp>
#include <dm/scada/measuremgr.hpp>
#include <dm/scada/cumulantmgr.hpp>
#include <dm/scada/actionmgr.hpp>
#include <dm/scada/logicdevice.hpp>

#include <dm/scada/rts.hpp>

namespace dm{
namespace app{

static const char* logModule = "CHisDataMgr.hisdata.app.dm";

CHisDataMgr::CHisDataMgr():m_his(dm::scada::CScadaHis::ins()),
		m_eventMgr(),m_measureMgr(),m_cumulantMgr(){

	m_enFrozen = false;
	m_enMeasure = false;
	m_enStatus = false;
	m_enDiscrete = false;
	m_enCumulant = false;
	m_enAction = false;

	m_autoClearStatus = false;
	m_autoClearDiscrete = false;
	m_autoClearMeasure = false;
	m_autoClearCumulant = false;

	std::string cfgFile = dm::env::getCfgFilePath("hisdata.xml");
	TiXmlDocument doc( cfgFile.c_str());

	if( !doc.LoadFile() ){
		log().error(THISMODULE "没有发现配置文件%s",cfgFile.c_str());
		return;
	}else{
		TiXmlElement* dm_his = doc.FirstChildElement("dm-his");
		if( dm_his==NULL ){
			log().error(THISMODULE "配置文件%s错误",cfgFile.c_str());
			return;
		}else{
			TiXmlElement* frozen = dm_his->FirstChildElement("frozen");
			if( frozen ){
				m_enFrozen = std::strcmp(frozen->Attribute("enable"),"true")==0;
				if( m_enFrozen ){
					TiXmlElement* trigger = frozen->FirstChildElement("trigger");
					while( trigger ){
						CTimeTrigger t;
						const char* att = trigger->Attribute("cycle");
						if( att==NULL ){
							log().error( THISMODULE "冻结触发器中没有设置cycle属性");
							return;
						}else if( std::strcmp(att,"yearly")==0 )
							t.setCycle(CTimeTrigger::Yearly);
						else if( std::strcmp(att,"monthly")==0 )
							t.setCycle(CTimeTrigger::Monthly);
						else if( std::strcmp(att,"daily")==0 )
							t.setCycle(CTimeTrigger::Daily);
						else if( std::strcmp(att,"hourly")==0 )
							t.setCycle(CTimeTrigger::Hourly);
						else if( std::strcmp(att,"minutely")==0 )
							t.setCycle(CTimeTrigger::Minutely);
						else{
							log().error( THISMODULE "触发器中未知的周期类型:%s",att);
							return;
						}

						att = trigger->Attribute("time");
						if( att==NULL ){
							log().error( THISMODULE "冻结触发器中没有设置time属性");
							return;
						}else{
							dm::CDateTime dt;
							if( !dt.fromString(att) ){
								log().error( THISMODULE "冻结触发器中time属性错误%s",att);
								return;
							}
							t.setTimeInfo(dt);
						}

						t.init();
						m_frozeTriggers.push_back(t);
						trigger = trigger->NextSiblingElement("trigger");
					}
				}
			}

			TiXmlElement* multiTable = dm_his->FirstChildElement("multi-table");
			if( multiTable ){
				const char* att = multiTable->Attribute("type");
				if( att==NULL ){
					m_his.setMultiTableType(m_his.MtNone);
				}else if( std::strcmp(att,"none")==0 ){
					m_his.setMultiTableType(m_his.MtNone);
				}else if( std::strcmp(att,"1min")==0 ){
					m_his.setMultiTableType(m_his.MtMinute);
				}else if( std::strcmp(att,"2min")==0 ){
					m_his.setMultiTableType(m_his.Mt2Minutes);
				}else if( std::strcmp(att,"5min")==0 ){
					m_his.setMultiTableType(m_his.Mt5Minutes);
				}else if( std::strcmp(att,"10min")==0 ){
					m_his.setMultiTableType(m_his.Mt10Minutes);
				}else if( std::strcmp(att,"20min")==0 ){
					m_his.setMultiTableType(m_his.Mt20Minutes);
				}else if( std::strcmp(att,"30min")==0 ){
					m_his.setMultiTableType(m_his.Mt30Minutes);
				}else if( std::strcmp(att,"1hour")==0 ){
					m_his.setMultiTableType(m_his.MtHour);
				}else if( std::strcmp(att,"2hour")==0 ){
					m_his.setMultiTableType(m_his.Mt2Hours);
				}else if( std::strcmp(att,"3hour")==0 ){
					m_his.setMultiTableType(m_his.Mt3Hours);
				}else if( std::strcmp(att,"4hour")==0 ){
					m_his.setMultiTableType(m_his.Mt4Hours);
				}else if( std::strcmp(att,"6hour")==0 ){
					m_his.setMultiTableType(m_his.Mt6Hours);
				}else if( std::strcmp(att,"8hour")==0 ){
					m_his.setMultiTableType(m_his.Mt8Hours);
				}else if( std::strcmp(att,"12hour")==0 ){
					m_his.setMultiTableType(m_his.Mt12Hours);
				}else if( std::strcmp(att,"day")==0 ){
					m_his.setMultiTableType(m_his.MtDay);
				}else if( std::strcmp(att,"month")==0 ){
					m_his.setMultiTableType(m_his.MtMonth);
				}else if( std::strcmp(att,"year")==0 ){
					m_his.setMultiTableType(m_his.MtYear);
				}else{
					log().warnning(THISMODULE "未知多表模式%s",att);
					m_his.setMultiTableType(m_his.MtNone);
				}
			}else{
				m_his.setMultiTableType(m_his.MtNone);
			}

			TiXmlElement* clear = dm_his->FirstChildElement("clear");
			if( clear ){
				TiXmlElement* trigger = clear->FirstChildElement("trigger");
				while( trigger ){
					CTimeTrigger t;
					const char* att = trigger->Attribute("cycle");
					if( att==NULL ){
						log().error( THISMODULE "清理触发器中没有设置cycle属性");
						return;
					}else if( std::strcmp(att,"yearly")==0 )
						t.setCycle(CTimeTrigger::Yearly);
					else if( std::strcmp(att,"monthly")==0 )
						t.setCycle(CTimeTrigger::Monthly);
					else if( std::strcmp(att,"daily")==0 )
						t.setCycle(CTimeTrigger::Daily);
					else if( std::strcmp(att,"hourly")==0 )
						t.setCycle(CTimeTrigger::Hourly);
					else if( std::strcmp(att,"minutely")==0 )
						t.setCycle(CTimeTrigger::Minutely);
					else{
						log().error( THISMODULE "清理触发器中未知的周期类型:%s",att);
						return;
					}

					att = trigger->Attribute("time");
					if( att==NULL ){
						log().error( THISMODULE "清理触发器中没有设置time属性");
						return;
					}else{
						dm::CDateTime dt;
						if( !dt.fromString(att) ){
							log().error( THISMODULE "清理触发器中time属性错误%s",att);
							return;
						}
						t.setTimeInfo(dt);
					}

					t.init();
					m_clearTriggers.push_back(t);
					trigger = trigger->NextSiblingElement("trigger");
				}
			}

			TiXmlElement* status = dm_his->FirstChildElement("status");
			if( status ){
				m_enStatus = (std::strcmp(status->Attribute("enable"),"true")==0);
				status->QueryUnsignedAttribute("duration",&m_statusDuration);
				m_autoClearStatus = (std::strcmp("auto",status->Attribute("clear"))==0);
			}

			TiXmlElement* discrete = dm_his->FirstChildElement("discrete");
			if( discrete ){
				m_enDiscrete = (std::strcmp(discrete->Attribute("enable"),"true")==0);
				discrete->QueryUnsignedAttribute("duration",&m_discreteDuration);
				m_autoClearDiscrete = (std::strcmp("auto",discrete->Attribute("clear"))==0);
			}

			TiXmlElement* measure = dm_his->FirstChildElement("measure");
			if( measure ){
				m_enMeasure = (std::strcmp(measure->Attribute("enable"),"true")==0);
				measure->QueryUnsignedAttribute("duration",&m_measureDuration);
				m_autoClearMeasure = (std::strcmp("auto",measure->Attribute("clear"))==0);
			}

			TiXmlElement* cumulant = dm_his->FirstChildElement("cumulant");
			if( cumulant ){
				m_enCumulant = (std::strcmp(cumulant->Attribute("enable"),"true")==0);
				cumulant->QueryUnsignedAttribute("duration",&m_cumulantDuration);
				m_autoClearCumulant = (std::strcmp("auto",cumulant->Attribute("clear"))==0);
			}

			TiXmlElement* action = dm_his->FirstChildElement("action");
			if( action ){
				m_enAction = (std::strcmp(action->Attribute("enable"),"true")==0);
				action->QueryUnsignedAttribute("duration",&m_actionDuration);
				m_autoClearAction = (std::strcmp("auto",action->Attribute("clear"))==0);
			}
		}
	}

	dm::CTimeStamp now = dm::CTimeStamp::cur();

	switch( m_his.multiTableType() ){
	case dm::scada::CScadaHis::MtNone:
		break;
	case dm::scada::CScadaHis::MtMinute:
	case dm::scada::CScadaHis::Mt2Minutes:
	case dm::scada::CScadaHis::Mt5Minutes:
	case dm::scada::CScadaHis::Mt10Minutes:
	case dm::scada::CScadaHis::Mt20Minutes:
	case dm::scada::CScadaHis::Mt30Minutes:
		m_refreshTrigger.setCycle(CTimeTrigger::Minutely);
		break;
	case dm::scada::CScadaHis::MtHour:
	case dm::scada::CScadaHis::Mt2Hours:
	case dm::scada::CScadaHis::Mt3Hours:
	case dm::scada::CScadaHis::Mt4Hours:
	case dm::scada::CScadaHis::Mt6Hours:
	case dm::scada::CScadaHis::Mt8Hours:
	case dm::scada::CScadaHis::Mt12Hours:
		m_refreshTrigger.setCycle(CTimeTrigger::Hourly);
		break;
	case dm::scada::CScadaHis::MtDay:
		m_refreshTrigger.setCycle(CTimeTrigger::Daily);
		break;
	case dm::scada::CScadaHis::MtMonth:
		m_refreshTrigger.setCycle(CTimeTrigger::Monthly);
		break;
	case dm::scada::CScadaHis::MtYear:
		m_refreshTrigger.setCycle(CTimeTrigger::Yearly);
		break;
	}

	m_refreshTrigger.init();

	m_nextRefresh = m_refreshTrigger.nextTime(now);

	m_his.refresh(now);

	snapStatus(now);
	snapDiscrete(now);
	snapMeasure(now);
	snapCumulant(now);

	getNextTriggerTime(m_nextFroze,now,m_frozeTriggers);
	getNextTriggerTime(m_nextClear,now,m_clearTriggers);

	log().debug(THISMODULE "加载配置文件%s",cfgFile.c_str());
}

CHisDataMgr& CHisDataMgr::ins(){
	static CHisDataMgr i;
	return i;
}

void CHisDataMgr::runOnce(){
	dm::CTimeStamp now = dm::CTimeStamp::cur();

	if( m_his.multiTableType()!=m_his.MtNone && m_nextRefresh<now ){
		// 刷新按月建表
		m_nextRefresh = m_refreshTrigger.nextTime(now);
		if(m_his.refresh(now)==false)
			log().error(THISMODULE "历史数据库刷新建表失败");
	}

	if( m_enFrozen ){
		if( m_nextFroze<=now ){
			log().info(THISMODULE "定时冻结数据%s",dm::CDateTime(now).toString().c_str());
			dm::scada::SHisRcdMeasure rcd;
			rcd.ts = now;
			rcd.op = now;
			rcd.cause = dm::scada::SHisRcdMeasure::Clocking;

			if( m_his.connect() ){
				for( int i=0;i<dm::scada::CMeasureMgr::ins().size();++i ){
					const dm::scada::SMeasureInfo* info = dm::scada::CMeasureMgr::ins().info(i);
					if( info ){
						if( info->isFlagSave() ){
							rcd.name = info->name;
							rcd.v = dm::scada::CMeasureMgr::ins().rt(i)->getValue().getValue();
							m_his.appendMeasure(rcd);
						}
					}else{
						log().warnning(THISMODULE "定时冻结 不能获取测量量信息idx=%d",i);
					}
				}
			}else{
				log().warnning(THISMODULE "连接数据库失败");
			}

			getNextTriggerTime(m_nextFroze,now,m_frozeTriggers);

			log().info(THISMODULE "下次冻结时间%s",dm::CDateTime(m_nextFroze).toString().c_str());
		}
	}

	if( m_enMeasure ){
		dm::scada::CTimedMeasure tm;
		bool of;
		while( m_measureMgr.tryGet(tm,of) ){
			if( of ){
				log().warnning(THISMODULE "时标测量量队列溢出");
				m_measureMgr.reset();
				break;
			}
				
			const dm::scada::SMeasureInfo* info = dm::scada::CMeasureMgr::ins().info(tm.getIndex());
			if( info ){
				if( info->isFlagSave() ){
					dm::scada::SHisRcdMeasure rcd;
					rcd.name = info->name;
					rcd.v = tm.getValue();
					rcd.ts = tm.getTimeStamp();
					rcd.op = now;
					rcd.cause = dm::scada::SHisRcdMeasure::Varied;

					if( m_his.connect() ){
						m_his.appendMeasure(rcd);
					}else{
						log().warnning(THISMODULE "连接数据库失败");
					}
				}
			}else{
				log().warnning(THISMODULE "不能获取测量量信息idx=%d",tm.getIndex());
			}
		}
	}

	if( m_enStatus||m_enDiscrete||m_enAction ){
		dm::scada::CEvent ev;
		bool of;
		while( m_eventMgr.tryGet(ev,of) ){
			if( of ){
				log().warnning(THISMODULE "事件队列溢出");
				m_eventMgr.reset();
				break;
			}
				
			if( ev.type==dm::scada::CEvent::Status ){
				if(  m_enStatus ){
					const dm::scada::SStatusInfo* info = dm::scada::CStatusMgr::ins().info(ev.idx);
					if( info ){
						if( info->isFlagSave() ){
							dm::scada::SHisRcdStatus rcd;
							rcd.name = info->name;
							rcd.v = ev.value.status;
							rcd.ts = ev.ts;
							rcd.op = now;
							rcd.cause = dm::scada::SHisRcdStatus::Varied;

							if( m_his.connect() ){
								m_his.appendStatus(rcd);
							}else{
								log().warnning(THISMODULE "连接数据库失败");
							}
						}
					}else{
						log().warnning(THISMODULE "不能获取状态量信息 idx=%d",ev.idx);
					}
				}
			}else if( ev.type==dm::scada::CEvent::Discrete ){
				if( m_enDiscrete ){
					const dm::scada::SDiscreteInfo* info = dm::scada::CDiscreteMgr::ins().info(ev.idx);
					if( info ){
						if( info->isFlagSave() ){
							dm::scada::SHisRcdDiscrete rcd;
							rcd.name = info->name;
							rcd.v = ev.value.discrete;
							rcd.ts = ev.ts;
							rcd.op = now;
							rcd.cause = dm::scada::SHisRcdDiscrete::Varied;

							if( m_his.connect() ){
								m_his.appendDiscrete(rcd);
							}else{
								log().warnning(THISMODULE "连接数据库失败");
							}
						}
					}else{
						log().warnning(THISMODULE "不能获取离散量信息 idx=%d",ev.idx );
					}
				}
			}else if( ev.type==dm::scada::CEvent::Action ){
				if( m_enAction  ){
					const dm::scada::SActionInfo* info = dm::scada::CActionMgr::ins().info(ev.idx);
					if( info ){
						if( info->isFlagSave() ){
							dm::scada::SHisRcdAction rcd;
							rcd.name = info->name;
							rcd.v = ev.value.discrete;
							rcd.ts = ev.ts;
							rcd.op = now;
							rcd.cause = dm::scada::SHisRcdAction::Varied;
							if( m_his.connect() ){
								m_his.appendAction(rcd);
							}else{
								log().warnning(THISMODULE "连接数据库失败");
							}
						}
					}else{
						log().warnning(THISMODULE "不能获取动作信息idx=%d",ev.idx);
					}
				}
			}
		}
	}

	if( m_enCumulant ){
		dm::scada::CTimedCumulant tc;
		bool of;
		while( m_cumulantMgr.tryGet(tc,of) ){
			if( of ){
				log().warnning(THISMODULE "时标累计量队列溢出");
				m_cumulantMgr.reset();
				break;
			}

			const dm::scada::SCumulantInfo* info = dm::scada::CCumulantMgr::ins().info(tc.getIndex());
			if( info ){
				if( info->isFlagSave() ){
					dm::scada::SHisRcdCumulant rcd;
					rcd.name = info->name;
					rcd.v = dm::scada::CCumulantMgr::ins().calcValue(tc.getIndex(),tc.getRaw());
					rcd.ts = tc.getTimeStamp();
					rcd.op = now;
					rcd.cause = dm::scada::SHisRcdCumulant::Varied;
					if( m_his.connect() ){
						m_his.appendCumulant(rcd);
					}else{
						log().warnning(THISMODULE "连接数据库失败");
					}
				}
			}else{
				log().warnning(THISMODULE "不能获取累计量信息idx=%d",tc.getIndex());
			}
		}
	}

	if( m_autoClearStatus||m_autoClearDiscrete||m_autoClearMeasure||m_autoClearCumulant||m_autoClearAction ){
		if( m_nextClear<now ){
			if( m_autoClearStatus ){
				dm::CTimeStamp ts(now.seconds()-m_statusDuration*60*60,now.useconds());
				if( m_his.connect() ){
					m_his.clearStatusBeforeAndComplement(ts);
					log().info(THISMODULE "状态量保存%d小时的数据，清除%s之前数据",m_statusDuration,dm::CDateTime(ts).toString().c_str());
				}else{
					log().warnning(THISMODULE "连接数据库失败");
				}
			}

			if( m_autoClearDiscrete ){
				dm::CTimeStamp ts(now.seconds()-m_discreteDuration*60*60,now.useconds());
				if( m_his.connect() ){
					m_his.clearDiscreteBeforeAndComplement(ts);
					log().info(THISMODULE "离散量保存%d小时的数据，清除%s之前数据",m_discreteDuration,dm::CDateTime(ts).toString().c_str());
				}else{
					log().warnning(THISMODULE "连接数据库失败");
				}
			}

			if( m_autoClearMeasure ){
				dm::CTimeStamp ts(now.seconds()-m_measureDuration*60*60,now.useconds());
				if( m_his.connect() ){
					m_his.clearMeasureBeforeAndComplement(ts);
					log().info(THISMODULE "测量量保存%d小时的数据，清除%s之前数据",m_measureDuration,dm::CDateTime(ts).toString().c_str());
				}else{
					log().warnning(THISMODULE "连接数据库失败");
				}
			}

			if( m_autoClearCumulant ){
				dm::CTimeStamp ts(now.seconds()-m_cumulantDuration*60*60,now.useconds());
				if( m_his.connect() ){
					m_his.clearCumulantBeforeAndComplement(ts);
					log().info(THISMODULE "累计量保存%d小时的数据，清除%s之前数据",m_cumulantDuration,dm::CDateTime(ts).toString().c_str());
				}else{
					log().warnning(THISMODULE "连接数据库失败");
				}
			}

			if( m_autoClearAction ){
				dm::CTimeStamp ts(now.seconds()-m_actionDuration*60*60,now.useconds());
				if( m_his.connect() ){
					m_his.clearActionBeforeAndComplement(ts);
					log().info(THISMODULE "动作保存%d小时的数据，清除%s之前数据",m_actionDuration,dm::CDateTime(ts).toString().c_str());
				}else{
					log().warnning(THISMODULE "连接数据库失败");
				}
			}

			getNextTriggerTime(m_nextClear,now,m_clearTriggers);
		}
	}

	m_his.tryUnlock();
}

/**
 * 获取下次执行时刻
 * @param ts
 * @param now
 * @param triggers
 */
void CHisDataMgr::getNextTriggerTime( dm::CTimeStamp& ts,const dm::CTimeStamp& now,std::list<CTimeTrigger>& triggers )const{
	if( triggers.size()<1 )
		return;

	std::list<CTimeTrigger>::iterator it=triggers.begin();
	dm::CTimeStamp t;
	ts = it->nextTime(now);
	++it;
	while( it!=triggers.end() ){
		t = it->nextTime(now);
		if( t<ts )
			ts = t;

		++it;
	}
}

void CHisDataMgr::snapStatus( const dm::CTimeStamp& now ){
	for( dm::scada::index_t idx=0;idx<dm::scada::CStatusMgr::ins().size();++idx ){
		const dm::scada::SStatusInfo* info = dm::scada::CStatusMgr::ins().info(idx);
		if( info->isFlagSave() ){
			dm::scada::SHisRcdStatus rcd;

			dm::scada::rt_s_t* rt = dm::scada::CStatusMgr::ins().rt(idx);

			rcd.name = info->name;
			rcd.v = rt->getValue().getValue();
			rcd.ts = rt->getTimeStamp();
			rcd.op = now;
			rcd.cause = dm::scada::SHisRcdStatus::Complement;

			if( m_his.connect() ){
				m_his.appendStatus(rcd);
			}else{
				log().warnning(THISMODULE "连接数据库失败");
			}
		}
	}
}

void CHisDataMgr::snapDiscrete( const dm::CTimeStamp& now ){
	for( dm::scada::index_t idx=0;idx<dm::scada::CDiscreteMgr::ins().size();++idx ){
		const dm::scada::SDiscreteInfo* info = dm::scada::CDiscreteMgr::ins().info(idx);
		if( info->isFlagSave() ){
			dm::scada::SHisRcdDiscrete rcd;

			dm::scada::rt_d_t* rt = dm::scada::CDiscreteMgr::ins().rt(idx);

			rcd.name = info->name;
			rcd.v = rt->getValue().getValue();
			rcd.ts = rt->getTimeStamp();
			rcd.op = now;
			rcd.cause = dm::scada::SHisRcdDiscrete::Complement;

			if( m_his.connect() ){
				m_his.appendDiscrete(rcd);
			}else{
				log().warnning(THISMODULE "连接数据库失败");
			}
		}
	}
}

void CHisDataMgr::snapMeasure( const dm::CTimeStamp& now ){
	for( dm::scada::index_t idx=0;idx<dm::scada::CMeasureMgr::ins().size();++idx ){
		const dm::scada::SMeasureInfo* info = dm::scada::CMeasureMgr::ins().info(idx);
		if( info->isFlagSave() ){
			dm::scada::SHisRcdMeasure rcd;

			dm::scada::rt_m_t* rt = dm::scada::CMeasureMgr::ins().rt(idx);

			rcd.name = info->name;
			rcd.v = rt->getValue().getValue();
			rcd.ts = rt->getTimeStamp();
			rcd.op = now;
			rcd.cause = dm::scada::SHisRcdMeasure::Complement;

			if( m_his.connect() ){
				m_his.appendMeasure(rcd);
			}else{
				log().warnning(THISMODULE "连接数据库失败");
			}
		}
	}
}

void CHisDataMgr::snapCumulant( const dm::CTimeStamp& now ){
	for( dm::scada::index_t idx=0;idx<dm::scada::CCumulantMgr::ins().size();++idx ){
		const dm::scada::SCumulantInfo* info = dm::scada::CCumulantMgr::ins().info(idx);
		if( info->isFlagSave() ){
			dm::scada::SHisRcdCumulant rcd;

			dm::scada::rt_c_t* rt = dm::scada::CCumulantMgr::ins().rt(idx);

			rcd.name = info->name;
			rcd.v = rt->getValue().getValue();
			rcd.ts = rt->getTimeStamp();
			rcd.op = now;
			rcd.cause = dm::scada::SHisRcdCumulant::Complement;

			if( m_his.connect() ){
				m_his.appendCumulant(rcd);
			}else{
				log().warnning(THISMODULE "连接数据库失败");
			}
		}
	}
}

}
}
